﻿//AcademicPlanner - course registration planning web app.
//Copyright (C) 2009  Boubacar Diallo

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using AcademicPlanner.Domain.Models;

namespace AcademicPlanner.Presentation
{
	/// <summary>
	/// FormModel represent the model object for a strongly typed form control.
	/// </summary>
	/// <since>1.0</since>
	public class FormModel : ControlModel
	{
		/// <summary>
		/// Gets or sets whether or not to show a cancel button next to the
		/// submit button of this FormModel's <c>Form</c>.
		/// </summary>
		/// <since>1.0</since>
		public bool IncludeCancelButton { get; set; }

		/// <summary>
		/// Gets the prefix to the names of the fields in the form; becomes
		/// very useful when embedding forms into other forms.
		/// </summary>
		/// <since>1.0</since>
		public string NamesPrefix { get; set; }
	}


	/// <summary>
	/// Form(TFormModel) is the base class for strongly typed form controls.
	/// </summary>
	/// <typeparam name="TFormModel">The type of the model object of the Form control.</typeparam>
	/// <since>1.0</since>
	public class Form<TFormModel> : Control<FormModel> where TFormModel : FormModel
    {
		/// <summary>
		/// The MVC action that this Form submits to.
		/// </summary>
		/// <since>1.0</since>
		protected string actionName;

		/// <summary>
		/// The MVC controller this Form submits to.
		/// </summary>
		/// <since>1.0</since>
		protected string controllerName;

		/// <summary>
		/// Parameters of the MVC route this Form submits to.
		/// </summary>
		/// <since>1.0</since>
		protected object routeValues;

		/// <summary>
		/// The HTTP method of this Form.
		/// </summary>
		/// <since>1.0</since>
		protected System.Web.Mvc.FormMethod method;
		

		/// <summary>
		/// Gets or sets this Form's name.
		/// </summary>
		/// <since>1.0</since>
		public string Name { get; protected set; }

		/// <summary>
		/// Gets or sets this Form's submit button text.
		/// </summary>
		/// <since>1.0</since>
		public string SubmitButtonValue { get; protected set; }


		/// <summary>
		/// Gets the complete field name of a property of a <c>Model</c> in this Form.
		/// </summary>
		/// <param name="pProperty">The property name.</param>
		/// <param name="pIndex">The index if the property is a collection; may be <code>null</code>.</param>
		/// <param name="pParentProperty">The name of the property's parent; may be <code>null</code>.</param>
		/// <returns>The complete field name of the <c>Model</c> property.</returns>
		/// <seealso cref="M:GetFieldName(string, int, string, int)"/>
		/// <since>1.0</since>
		protected string GetFieldName(string pProperty, int? pIndex, string pParentProperty)
		{
			return GetFieldName(pProperty, pIndex, pParentProperty, null);
		}

		/// <summary>
		/// Gets the complete field name of a property of a <c>Model</c> for this Form.
		/// </summary>
		/// <remarks>
		/// This method is a convenience call to <see cref="M:Form.GetFieldName(string, string, int, string, int)"/> 
		/// with this Form's <c>NamesPrefix</c>.
		/// </remarks>
		/// <param name="pProperty">The property name.</param>
		/// <param name="pIndex">The index if the property is a collection; may be <code>null</code>.</param>
		/// <param name="pParentProperty">The name of the property's parent; may be <code>null</code>.</param>
		/// <param name="pParentIndex">
		/// The index of the property's parent if the parent is a collection; may be <code>null</code>.
		/// </param>
		/// <returns>The complete field name of the <c>Model</c> property.</returns>
		/// <seealso cref="M:Form.GetFieldName(string, string, int, string, int)"/>
		/// <since>1.0</since>
		protected string GetFieldName(string pProperty, int? pIndex, string pParentProperty, int? pParentIndex)
		{
			return Form.GetFieldName(Model.NamesPrefix, pProperty, pIndex, pParentProperty, pParentIndex);
		}

		/// <summary>
		/// Gets the complete field name of a property of a <c>Model</c> for this Form.
		/// </summary>
		/// <remarks>
		/// This method is a convenience overload of <see cref="M:GetFieldName(string, int, string)"/> 
		/// with the third argument <c>null</c>.
		/// </remarks>
		/// <param name="pProperty">The property name.</param>
		/// <param name="pIndex">The index if the property is a collection; may be <code>null</code>.</param>
		/// <returns>The complete field name of the <c>Model</c> property.</returns>
		/// <since>1.0</since>
		protected string GetFieldName(string pProperty, int? pIndex)
		{
			return GetFieldName(pProperty, pIndex, null);
		}

		/// <summary>
		/// Gets the complete field name of a property of a <c>Model</c> for this Form.
		/// </summary>
		/// <remarks>
		/// This method is a convenience overload of <see cref="M:GetFieldName(string, int, string, int)"/> 
		/// with the second argument <c>null</c>.
		/// </remarks>
		/// <param name="pProperty">The property name.</param>
		/// <param name="pParentProperty">The name of the property's parent; may be <code>null</code>.</param>
		/// <param name="pParentIndex">
		/// The index of the property's parent if the parent is a collection; may be <code>null</code>.
		/// </param>
		/// <returns>The complete field name of the <c>Model</c> property.</returns>
		/// <since>1.0</since>
		protected string GetFieldName(string pProperty, string pParentProperty, int? pParentIndex)
		{
			return GetFieldName(pProperty, null, pParentProperty, pParentIndex);
		}

		/// <summary>
		/// This method is a convenience overload of <see cref="M:GetFieldName(string, int, string)"/> 
		/// with the second parameter <code>null</code>.
		/// </summary>
		/// <param name="pProperty">The property name.</param>
		/// <param name="pParentProperty">The name of the property's parent; may be <code>null</code>.</param>
		/// <returns>The complete field name of the <c>Model</c> property.</returns>
		/// <since>1.0</since>
		protected string GetFieldName(string pProperty, string pParentProperty)
		{
			return GetFieldName(pProperty, null, pParentProperty);
		}

		/// <summary>
		/// This method is a convenience overload of <see cref="M:GetFieldName(string, string)"/> 
		/// with the second parameter <code>""</code>.
		/// </summary>
		/// <param name="pProperty">The property name.</param>
		/// <returns>The complete field name of the <c>Model</c> property.</returns>
		/// <since>1.0</since>
		protected string GetFieldName(string pProperty)
		{
			return GetFieldName(pProperty, "");
		}

		/// <summary>
		/// Prints a html form opening tag for this Form.
		/// </summary>
		/// <returns>The MVC html representation of this Form's html.</returns>
		/// <since>1.0</since>
		protected MvcForm Begin()
		{
			return Html.BeginForm(actionName, controllerName, routeValues, method,
					new { name = Name, id = Model.Id, autocomplete = "on", onsubmit = "return gFormsCtrl.submit(event, this);" });
		}

		/// <summary>
		/// Prints an html form closing tag for this Form.
		/// </summary>
		/// <since>1.0</since>
		protected void End()
		{
			Html.EndForm();
		}

    }


	/// <summary>
	/// Form is the class encapsulating static operations for form controls.
	/// </summary>
	/// <since>1.0</since>
	public class Form : Control<FormModel>
	{
		/// <summary>
		/// Gets the seperator used when constructing field names for <code>Model</code> properties.
		/// </summary>
		/// <since>1.0</since>
		public static string Separator { get { return "_"; } }

		/// <summary>
		/// Gets the default input errors summary header; 
		/// returns the translated message "Please fix the following input errors:".
		/// </summary>
		/// <since>1.0</since>
		public static string ErrorsSummaryHeader { get { return Tr("Please fix the following input errors:"); } }

		/// <summary>
		/// Gets the default input errors summary footer; returns an emtpy <c>string</c>.
		/// </summary>
		/// <since>1.0</since>
		public static string ErrorsSummaryFooter { get { return ""; } }


		/// <summary>
		/// Constructs the complete field name to be used in a html form for a <code>Model</code> property.
		/// </summary>
		/// <remarks>
		/// The property is assumed to be a direct(simple) property such as an <c>int, double, string, date</c>, etc
		///  and not another <code>Model</code> under the given <code>Model</code> in which case the 
		/// <paramref name="pParentProperty" /> is used to nest the direct(simple) property.
		/// The general format of a <code>Model</code> property's field name is:
		///		[Prefix (if embedding or partitionning for eg) + Separator]
		///		[Name of parent (if property is nested) + Separator[Index of Parent (if parent is a collection)]]
		///		Name of property
		///		[Separator + index if property is a collection]
		///	Example outputs are:
		///		PropertyOne_PropertyTwoOfTypeOfPropertyOne
		///		PropertyOne_0_PropertyTwoOfTypeOfPropertyOne
		///		PropertyOne_0_PropertyTwoOfTypeOfPropertyOne_1
		///		PropertyOne_PropertyTwoOfTypeOfPropertyOne_2
		/// </remarks>
		/// <param name="pNamesPrefix">The prefix; may be <c>null</c>.</param>
		/// <param name="pProperty">The name of the property.</param>
		/// <param name="pIndex">The index if the property is a collection; may be <c>null</c>.</param>
		/// <param name="pParentProperty">The name of the property's parent if nested; may be <c>null</c>.</param>
		/// <param name="pParentIndex">
		/// The index of the property's parent if the parent is a collection; may be <code>null</code>.
		/// </param>
		/// <returns>The complete field name for the <code>Model</code> property.</returns>
		/// <since>1.0</since>
		public static string GetFieldName(string pNamesPrefix, string pProperty, int? pIndex, string pParentProperty, int? pParentIndex)
		{
			StringBuilder vBuilder = new StringBuilder();

			if (!string.IsNullOrEmpty(pNamesPrefix))
				vBuilder.Append(pNamesPrefix).Append(Separator);
			if (!string.IsNullOrEmpty(pParentProperty))
			{
				vBuilder.Append(pParentProperty).Append(Separator);
				if (pParentIndex != null)
					vBuilder.Append(pParentIndex).Append(Separator);
			}
			vBuilder.Append(pProperty);
			if (pIndex != null)
				vBuilder.Append(Separator).Append(pIndex);

			return vBuilder.ToString();
		}

		/// <summary>
		/// Constructs an html summary of input errors. This method recursively reads
		/// the inner error information contained in the errors collection <c>InnerErrors</c>
		/// while straightly appending the one in <c>OuterErrors</c>. When header and footer
		/// are provided, they are wrapped around paragraph tags.
		/// </summary>
		/// <param name="pErrors">The collection of errors.</param>
		/// <param name="pHeader">A header text/html; may be <c>null</c>.</param>
		/// <param name="pFooter">A footer text/html; may be <c>null</c>.</param>
		/// <returns>The html summary of input errors.</returns>
		/// <seealso cref="M:AcademicPlanner.Domain.Models.InvalidModelPropertiesException.GetErrorCodeMessageFormat"/>
		/// <seealso cref="M:Labels.Get(string, string)"/>
		/// <since>1.0</since>
		public static string SummarizeErrors(InvalidModelPropertiesErrorCollection pErrors,
				string pHeader, string pFooter)
		{
			var vBuilder = new StringBuilder();

			if (pHeader != null)
			{
				vBuilder.Append(string.Format("<p>{0}</p>", pHeader)).Append("<ul>");
			}

			foreach (KeyValuePair<string, InvalidModelPropertyErrorCode> vPair in pErrors.OuterErrors)
			{
				vBuilder.Append("<li>")
					.Append(string.Format(Tr(InvalidModelPropertiesException.GetErrorCodeMessageFormat(vPair.Value)),
											Tr(Labels.Get(vPair.Key, pErrors.ModelName))))
					.Append("</li>");
			}

			foreach (KeyValuePair<string, InvalidModelPropertiesErrorCollection> vPair in pErrors.InnerErrors)
			{
				vBuilder.Append(SummarizeErrors(vPair.Value, null, null));
			}

			if (pFooter != null)
			{
				vBuilder.Append("</ul>").Append(string.Format("<p>{0}</p>", pFooter));
			}

			return vBuilder.ToString();
		}

		/// <summary>
		/// Convenience overload of <see cref="M:SummarizeErrors(InvalidModelPropertiesErrorCollection, 
		/// string, string)"/> with the second argument set to <see cref="P:ErrorsSummaryHeader"/>
		/// and the third one to <see cref="P:ErrorsSummaryFooter"/>.
		/// </summary>
		/// <param name="pErrors">The collection of error information.</param>
		/// <returns>The html summary of input errors.</returns>
		/// <since>1.0</since>
		public static string SummarizeErrors(InvalidModelPropertiesErrorCollection pErrors)
		{
			return SummarizeErrors(pErrors, ErrorsSummaryHeader, ErrorsSummaryFooter);
		}

		/// <summary>
		/// Constructs an html summary of a single input error.
		/// </summary>
		/// <param name="pError">The error text.</param>
		/// <param name="pHeader">A header text/html; may be <c>null</c>.</param>
		/// <param name="pFooter">A footer text/html; may be <c>null</c>.</param>
		/// <returns>The html summary of the input error.</returns>
		/// <since>1.0</since>
		public static string SummarizeError(string pError, string pHeader, string pFooter)
		{
			var vBuilder = new StringBuilder();

			if (pHeader != null)
			{
				vBuilder.Append(string.Format("<p>{0}</p>", pHeader)).Append("<ul>");
			}
			vBuilder.Append("<li>").Append(pError).Append("</li>");
			if (pFooter != null)
			{
				vBuilder.Append("</ul>").Append(string.Format("<p>{0}</p>", pFooter));
			}

			return vBuilder.ToString();
		}

		/// <summary>
		/// Convenience overload of <see cref="M:SummarizeError(string, string, string)"/> 
		/// with the second argument set to <see cref="P:ErrorsSummaryHeader"/>
		/// and the third one to <see cref="P:ErrorsSummaryFooter"/>.
		/// </summary>
		/// <param name="pError"></param>
		/// <returns>The html summary of the input error.</returns>
		/// <since>1.0</since>
		public static string SummarizeError(string pError)
		{
			return SummarizeError(pError, ErrorsSummaryHeader, ErrorsSummaryFooter);
		}
	}
}
